﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using KSATouch;
using KSAWPFExtensions;

using System.IO;
using System.Windows.Controls.Primitives;
using System.Windows.Media.Animation;
using System.Threading;

// Async image loading courtesy of Marzio Pattano
// http://dotnetlearning.wordpress.com/2011/01/27/loading-images-asynchronously-in-wpf/
using PhotoLoader; 

namespace KSAWPFDemo.PhotoBrowser
{
    /// <summary>
    /// Interaction logic for PhotoBrowser.xaml
    /// </summary>
    public partial class PhotoBrowser : UserControl
    {
        const int DelayTimeToZoom = 650;

        private KSAWPFWindow mainWindow;

        private Random random;
        private int currentIndex;
        private List<Canvas> photoCanvases;
        
        // track one contact 'from the side' for hover
        int trackedID = -1;


        // track height
        DateTime LastChange = DateTime.Now;

        // Generic animation
        DoubleAnimation fadeIn = new DoubleAnimation(1, TimeSpan.FromMilliseconds(550));
        DoubleAnimation fadeOut = new DoubleAnimation(0, TimeSpan.FromMilliseconds(550));

        //When zooming in, the content starts at a transform of 1, and then changes.
        DoubleAnimation zoomIn = new DoubleAnimation(1, TimeSpan.FromMilliseconds(400));

        //When zooming out, the content ends up either scaled larger (up) or smaller (down)
        DoubleAnimation zoomOutDown = new DoubleAnimation(1.5, TimeSpan.FromMilliseconds(600));
        DoubleAnimation zoomOutUp = new DoubleAnimation(.3, TimeSpan.FromMilliseconds(600));


        public PhotoBrowser()
        {            
            InitializeComponent();
            random = new Random((int)DateTime.Now.Ticks);
            photoCanvases = new List<Canvas>();
            currentIndex = 0;
            fadeIn.EasingFunction = new QuadraticEase();
            fadeOut.EasingFunction = new QuadraticEase();
            fadeIn.Completed += (sender, e3) =>
                {
                   // FillCanvasWithHQPhotos(currentIndex);
                };

            this.Loaded += PhotoBrowser_Loaded;
        }

        void PhotoBrowser_Loaded(object sender, RoutedEventArgs e)
        {
            mainWindow = KSAWPFWindow.GetWindow(this) as KSAWPFWindow;
            mainWindow.FrameReady += mainWindow_FrameReady;

            List<string> folders = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)).ToList();
            folders.Add(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures));

            int progressNum = folders.Count();
            int currentProgress = 0;
            foreach (string folder in folders)
            {
                Canvas canvas = FillCanvasWithPhotos(folder);
                canvas.IsHitTestVisible = false;
                if (canvas.Children.Count > 0)
                {
                    photoCanvases.Add(canvas);
                }
                currentProgress++;
                HeightSlider.Visibility = System.Windows.Visibility.Hidden;
            }
            currentIndex = 0;
            EnablePhotoCanvas(currentIndex, Direction.Down);

            mainWindow.PreviewKeyDown += ParentGrid_PreviewKeyDown;

        }

        /// <summary>
        /// Hover event - Records the current height to the slider. Then checks to see if zoomIn/Out should be called
        /// based on height of a contact off to the (right) side of the screen.
        /// TODO: Remove magic numbers for the right side, also make it work for both sides.
        /// </summary>
        /// <param name="Sender"></param>
        /// <param name="e"></param>
        void mainWindow_FrameReady(object Sender, KSATouch.FrameReadyEventArgs e)
        {
            
            if ((e.HoveringContacts.Count > 0) && (e.CurrentContacts.Count == 0))
            {
                if (trackedID == -1)
                {
                    foreach (Contact contact in e.HoveringContacts)
                    {
                        if ((contact.CenterPoint.X > 1750) && (contact.ContactZHeight > 1))
                        {
                            // contact detected 'in range'...assign its id
                            trackedID = contact.Id;
                            HeightSlider.Visibility = Visibility.Visible;
                        }
                    }
                }
                else
                {
                    // only change once every few seconds
                    if (DateTime.Now.Subtract(LastChange) < TimeSpan.FromMilliseconds(DelayTimeToZoom))
                    {
                        return;
                    }
                    Contact hoverContact = e.HoveringContacts.Where(con => con.Id == trackedID).FirstOrDefault();
                    if (hoverContact == null)
                    {
                        trackedID = -1;
                        LastChange = DateTime.Now;
                        HeightSlider.Visibility = System.Windows.Visibility.Hidden;
                        return;
                    }
                    int height = hoverContact.ContactZHeight;
                    if ((hoverContact.CenterPoint.X > 1200) && (height > 1))
                    {
                        HeightSlider.Visibility = System.Windows.Visibility.Visible;
                        if (height > 0)
                        {
                            HeightSlider.Value = height;
                            statusLabel.Content = "Hover Detected at: " + height;
                            if (height < 180)
                            {
                                ZoomDown();
                                LastChange = DateTime.Now;
                            }
                            if (height > 260)
                            {
                                ZoomUp();
                                LastChange = DateTime.Now;
                            }
                        }
                    }
                    else
                    {
                        // contacts 'out of range'
                        trackedID = -1;
                        HeightSlider.Visibility = Visibility.Hidden;
                    }

                } // end else (trackedid != -1
            }
  
            // begin old code
            /*
            int height = 0;
            if ((e.HoveringContacts.Count > 0) && (e.CurrentContacts.Count == 0))
            {
                foreach (Contact contact in e.HoveringContacts)
                {
                    height = e.HoveringContacts.First().ContactZHeight;
                    // if on the right side of the screen
                    if ((contact.CenterPoint.X > 1600) && (contact.ContactZHeight > 1))
                    {
                        if (height > 0)
                        {
                            HeightSlider.Value = height;
                        }
                    }
                }


                // only change once every few seconds
                if (DateTime.Now.Subtract(LastChange) < TimeSpan.FromMilliseconds(DelayTimeToZoom))
                {
                    return;
                }
                foreach (Contact contact in e.HoveringContacts)
                {
                    // if on the right side of the screen
                    if ((contact.CenterPoint.X > 1600) && (contact.ContactZHeight > 1))
                    {                        
                        if (height > 0)
                        {
                            HeightSlider.Value = height;
                            statusLabel.Content = "Hover Detected at: " + height;
                            if (height < 180)
                            {
                                ZoomDown();
                                LastChange = DateTime.Now;
                            }
                            if (height > 260)
                            {
                                ZoomUp();
                                LastChange = DateTime.Now;
                            }
                        }

                    }
                }
            }
            */
            //MoveItemsAwayFromObjects(e.HoveringContacts);

        }

        // Prototype idea to have pictures move away when a glass/drink is set down
        private void MoveItemsAwayFromObjects(List<Contact> hoveringContacts)
        {
            /*
            foreach (Contact contact in hoveringContacts)
            {
                foreach (MediaElement element in photoCanvases[currentIndex].Children)
                {
                    Rect rect = new Rect(element.PointToScreen(new Point(0, 0)),
                             element.PointToScreen(new Point(element.Width, element.Height)));

                    Rect contactRect = new Rect(contact.CenterPoint, new Size(100, 100));
                    if (contactRect.IntersectsWith(rect))
                    {
                        // ideally we'd move the item here, or fade it out and refade it in
                        // elsewhere
                    }
                }
            }
             * */
        }

        #region Layer Manipulations
        /// <summary>
        /// Called by zoomup/down to bring new canvas into effect.
        /// Should materialize all high quality images over the low quality ones here       
        /// </summary>
        /// <param name="index"></param>
        /// <param name="direction"></param>
        void EnablePhotoCanvas(int index, Direction direction)
        {
            // Clear off HD canvas
            photoCanvases[index].IsHitTestVisible = true;
            ScaleTransform transform = new ScaleTransform();
            if (direction == Direction.Down)
            {
                transform = new ScaleTransform(2, 2);
                photoCanvases[index].RenderTransform = transform;
                transform.BeginAnimation(ScaleTransform.ScaleXProperty, zoomIn);
                transform.BeginAnimation(ScaleTransform.ScaleYProperty, zoomIn);
            }
            else if (direction == Direction.Up) // explicit in case we add more enum values
            {
                transform = new ScaleTransform(.3, .3);
                photoCanvases[index].RenderTransform = transform;
            }
            // update the folder path label
            currentFolder.Content = photoCanvases[currentIndex].Tag as string;

            transform.BeginAnimation(ScaleTransform.ScaleXProperty, zoomIn);
            transform.BeginAnimation(ScaleTransform.ScaleYProperty, zoomIn);

            photoCanvases[index].BeginAnimation(Canvas.OpacityProperty, fadeIn);
            // fill in HD Grid
            //FillCanvasWithHQPhotos(index);
            
        }
        void DisablePhotoCanvas(int index, Direction direction)
        {
            ScaleTransform transform = new ScaleTransform(1, 1);
            photoCanvases[index].RenderTransform = transform;

            if (direction == Direction.Down)
            {
                transform.BeginAnimation(ScaleTransform.ScaleXProperty, zoomOutDown);
                transform.BeginAnimation(ScaleTransform.ScaleYProperty, zoomOutDown);
            }
            else if (direction == Direction.Up) // explicit in case we add more enum values
            {
                transform.BeginAnimation(ScaleTransform.ScaleXProperty, zoomOutUp);
                transform.BeginAnimation(ScaleTransform.ScaleYProperty, zoomOutUp);
            }

            photoCanvases[index].BeginAnimation(Canvas.OpacityProperty, fadeOut);
            photoCanvases[index].IsHitTestVisible = false;
            RemoveHQPhotosFromCanvas(index);
        }

        void ZoomDown()
        {
            if (currentIndex < photoCanvases.Count() - 1)
            {
                currentIndex++;
                DisablePhotoCanvas(currentIndex - 1, Direction.Up);
                EnablePhotoCanvas(currentIndex, Direction.Down);
            }
        }

        void ZoomUp()
        {
            if (currentIndex > 0)
            {
                currentIndex--;
                DisablePhotoCanvas(currentIndex + 1, Direction.Down);
                EnablePhotoCanvas(currentIndex, Direction.Up);
            }
        }
        #endregion

        #region Keyboard Controls
        void ParentGrid_PreviewKeyDown(object sender, KeyEventArgs e)
        {

            if (e.Key == Key.Add)
            {
                ZoomDown();
            }
            else if (e.Key == Key.Subtract)
            {
                ZoomUp();
            }
        }
        #endregion

        void FillCanvasWithHQPhotos(int index)
        {
            foreach (Grid grid in photoCanvases[index].Children)
            {                
                if (grid.Children.Count < 2)
                {
                    Image thumbnail = grid.Children[0] as Image;
                    Image image = new Image();
                    string path = thumbnail.Tag as string;
                    Loader.SetSource(image, path);
                    Loader.SetDisplayOption(image, DisplayOptions.FullResolution);
                    Loader.SetDisplayWaitingAnimationDuringLoading(image, true);
                    Loader.SetDisplayErrorThumbnailOnError(image, true);
                    image.Height = thumbnail.Height;
                    grid.Children.Insert(1, image);
                }
            }        
        }


        private void RemoveHQPhotosFromCanvas(int index)
        {
            foreach (Grid grid in photoCanvases[index].Children)
            {
                Image imagethumb = new Image();
                if (grid.Children.Count > 1)
                {
                    foreach (Image image in grid.Children)
                    {
                        if (image.Tag != null)
                        {
                            imagethumb = image;
                            imagethumb.Opacity = 1;
                            break;
                        }
                    }
                    grid.Children.Clear();
                    grid.Children.Add(imagethumb);
                }
            }
        }

        /// <summary>
        /// Generic - fill all child canvases with thumbnail images. This gives the impression of super fast performance when zooming
        /// 
        /// </summary>
        /// <param name="photoPath"></param>
        /// <returns></returns>
        Canvas FillCanvasWithPhotos(string photoPath)
        {
            Canvas canvas = new Canvas();
            canvas.RenderTransformOrigin = new Point(.5, .5); // ensures zoom animation originates from the center of the canvas
            string[] filePaths = Directory.GetFiles(photoPath,"*.jpg");

            if (filePaths != null)
            {
                canvas.Tag = filePaths;
                foreach (string filePath in filePaths)
                {
                    try
                    {
                        // create a random matrix rotation to place our element
                        Matrix matrix = new Matrix();
                        matrix.Rotate(90 * random.NextDouble());

                        matrix.OffsetX = random.Next(300, 700);
                        matrix.OffsetY = random.Next(300, 700);

                        // build our element..first an outer grid
                        Grid grid = new Grid();
                        grid.IsManipulationEnabled = true;
                        grid.RenderTransform = new MatrixTransform(matrix); 

                        // Create an image preview
                        Image image = new Image();
                        image.Height = 300;
                        image.Opacity = .5;
                        image.Tag = filePath;

                        // Start loading content
                        Loader.SetDisplayOption(image, DisplayOptions.Preview);
                        Loader.SetSource(image, filePath);
                        Loader.SetDisplayWaitingAnimationDuringLoading(image, true);
                        Loader.SetDisplayErrorThumbnailOnError(image, true);
                        
                        
                        // Add our image preview to the grid, then add both of those to the main canvas
                        grid.Children.Add(image);
                        canvas.Children.Add(grid);                                                
                    }
                    catch (Exception)
                    {

                    }
                }
            }
            canvas.ManipulationStarting += new EventHandler<ManipulationStartingEventArgs>(image_ManipulationStarting);
            canvas.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(image_ManipulationDelta);
            canvas.ManipulationCompleted+=canvas_ManipulationCompleted;
            canvas.Opacity = 0;
            if (canvas.Children.Count > 0)
            {
                ParentGrid.Children.Add(canvas);
            }
            return canvas;
        }

        void canvas_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            // MAKERCHANGE: If this crashes, comment out below
 	        if (e.Source.GetType() == typeof(Grid))
            {
                LoadHQImage((Grid)e.Source);
            }
        }

        void image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            Canvas currentCanvas = sender as Canvas;
            if (currentCanvas == null)
                return;

            // MAKER: Comment this back in to flip the behavior
            /*
            if (e.Source.GetType() == typeof(Grid))
            {
                LoadHQImage((Grid)e.Source);
            }
            */
            e.ManipulationContainer = currentCanvas;
        }

        /// <summary>
        /// Each photo is actually inside a grid. Inject another image in the grid over the top of the preview
        /// </summary>
        /// <param name="grid"></param>
        private void LoadHQImage(Grid grid)
        {
            foreach (UIElement g in photoCanvases[currentIndex].Children)
            {
                Canvas.SetZIndex(g, 0);
                Canvas.SetZIndex(grid, 1);
            }
            if (grid.Children.Count < 2)
            {
                Image thumbnail = grid.Children[0] as Image;
                Image image = new Image();
                string path = thumbnail.Tag as string;
                Loader.SetSource(image, path);
                Loader.SetDisplayOption(image, DisplayOptions.FullResolution);
                Loader.SetDisplayWaitingAnimationDuringLoading(image, true);
                Loader.SetDisplayErrorThumbnailOnError(image, true);
                
                image.Height = thumbnail.Height;
                grid.Children.Insert(1, image);
                //int zIndex = Canvas.GetZIndex(grid);
                //Canvas.SetZIndex(grid, zIndex + 100);
            }

        }

        void image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            //this just gets the source. 
            // I cast it to FE because I wanted to use ActualWidth for Center. You could try RenderSize as alternate
            var element = e.Source as FrameworkElement;
            if (element != null)
            {
                //e.DeltaManipulation has the changes 
                // Scale is a delta multiplier; 1.0 is last size,  (so 1.1 == scale 10%, 0.8 = shrink 20%) 
                // Rotate = Rotation, in degrees
                // Pan = Translation, == Translate offset, in Device Independent Pixels 


                var deltaManipulation = e.DeltaManipulation;
                var matrix = ((MatrixTransform)element.RenderTransform).Matrix;
                // find the old center; arguaby this could be cached 
                Point center = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
                // transform it to take into account transforms from previous manipulations 
                center = matrix.Transform(center);
                //this will be a Zoom. 
                matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, center.X, center.Y);
                // Rotation 
                matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);
                //Translation (pan) 
                matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);

                ((MatrixTransform)element.RenderTransform).Matrix = matrix;               

                e.Handled = true;

                System.Diagnostics.Debug.WriteLine("Matrix: " + matrix.ToString());
            }
        }
    }
    public enum Direction { Down, Up }
}
